package com.kedacom.ctsp.authority.service.simple;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Sets;
import com.kedacom.ctsp.authority.dao.DepartmentJpaDao;
import com.kedacom.ctsp.authority.entity.Role;
import com.kedacom.ctsp.authority.entity.User;
import com.kedacom.ctsp.authority.service.DepartmentService;
import com.kedacom.ctsp.authority.service.ResourceSettingRelationService;
import com.kedacom.ctsp.authority.service.UserService;
import com.kedacom.ctsp.authz.AuthenticationService;
import com.kedacom.ctsp.authz.entity.*;
import com.kedacom.ctsp.authz.exception.InternalLoginException;
import com.kedacom.ctsp.authz.exception.UserLoginException;
import com.kedacom.ctsp.authz.exception.UserLoginStatusIllegalException;
import com.kedacom.ctsp.authz.exception.UsernameNotFoundException;
import com.kedacom.ctsp.authz.security.provider.UserBean;
import com.kedacom.ctsp.lang.mapper.BeanMapper;
import com.kedacom.ctsp.lang.validate.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.kedacom.ctsp.authz.entity.ResourceEnum.DEPARTMENT_OF_CHARGE;


/**
 * 用户权限信息初始化类
 *
 * @author sunchenjie
 * @date 2017/11/22 0022
 */
@Slf4j
public class DefaultAuthenticationService implements AuthenticationService {

    @Autowired
    private UserService userService;
    @Autowired
    private ResourceSettingRelationService resourceSettingRelationService;
    @Autowired
    private DepartmentService departmentService;

    @Autowired
    private DepartmentJpaDao departmentJpaDao;

    private LoadingCache<String, Authentication> authenticationLoadingCache = CacheBuilder.newBuilder()
            .expireAfterWrite(30, TimeUnit.SECONDS)
            .build(new CacheLoader<String, Authentication>() {
                @Override
                public Authentication load(String key) {
                    return loadingAuthentication(key);
                }
            });

    private Authentication loadingAuthentication(String username) {
        log.info("loadUserByUsername [{}]", username);
        User user = userService.selectByUsername(username);
        // 用户不存在
        if (user == null) {
            throw new UsernameNotFoundException();
        }
        checkUserAndDeptCorrect(user);
        // 用户状态被禁用
        if (!userService.checkLoginUserStatus(user)) {
            throw new UserLoginStatusIllegalException();
        }

        return initUserAuthorization(user);
    }

    @Override
    public Authentication loadUserByUsername(String username) throws UserLoginException {
        try {
            return authenticationLoadingCache.get(username);
        } catch (ExecutionException e) {
            if (e.getCause() instanceof UserLoginException) {
                throw (UserLoginException) e.getCause();
            } else {
                throw new InternalLoginException(e.getCause());
            }
        }
    }

    /**
     * 登出,清空缓存
     *
     * @param username
     */
    @Override
    public void logout(String username) {
        log.info("清空缓存,用户名: [{}]", username);
        authenticationLoadingCache.invalidate(username);
    }

    private void checkUserAndDeptCorrect(User user) {
        log.info("判断用户是否在某个部门下");
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        UserBean userBean = (UserBean) httpServletRequest.getAttribute("authUser");
        if (userBean != null && StringUtils.isNotBlank(userBean.getDepartmentCode())) {
            if (user.getPerson() != null && user.getPerson().getDepartment() != null && StringUtils.isNotBlank(user.getPerson().getDepartment().getCode())) {
                if (!userBean.getDepartmentCode().equals(user.getPerson().getDepartment().getCode())) {
                    throw new ValidationException("当前上传的部门编号" + userBean.getDepartmentCode() + ",与用户实际的部门编号不符" + user.getPerson().getDepartment().getCode());
                }
            }
        }
    }


    /**
     * 初始化用户的权限信息
     *
     * @param user cache : pk : UID, sk: resourceId, value: AuthResource
     * @return
     */
    private Authentication initUserAuthorization(User user) {
        log.info("initUserAuthorization ");
        SimpleAuthentication authentication = new SimpleAuthentication();
        // 用户信息
        AuthUser authUser = new AuthUser();
        authUser.setId(user.getId());
        authUser.setName(user.getName());
        authUser.setUsername(user.getUsername());
        authentication.setUser(authUser);
        authentication.setPassword(user.getPassword());
        log.info("组装user id: {}, username: {}", authentication.getUser().getId(), authentication.getUser().getUsername());

        // 人员信息
        AuthPerson person = userService.getAuthPersonByUser(authentication.getUser());
        authentication.setPerson(person);
        if (authentication.getPerson() != null) {
            log.info("组装person id: {}, name: {}", authentication.getPerson().getId(), authentication.getPerson().getName());
        }

        // 部门
        String departmentCode = null;
        if (authentication.getPerson() != null) {
            departmentCode = authentication.getPerson().getDeptCode();
            log.info("部门 departmentCode: {} ", departmentCode);
        }
        log.info("查询角色 userId: {} , departmentCode", user.getId(), departmentCode);
        Set<AuthRole> roles = getAuthRolesByUidAndDCode(user.getId(), departmentCode);
        // 角色
        authentication.setRoles(roles);
        log.info("查询到角色: " + roles.size());

        log.info("initUserAuthorization return");
        return authentication;
    }

    private Set<AuthResource> getAuthResourcesOfChargedUsers(AuthPerson person) {
        if (person == null) {
            return Sets.newHashSet();
        }

        //所有资源
        Set<AuthResource> authResources = new HashSet<>();

        //分管的人
        Set<String> peopleOfCharge = person.getChargedUsers();
        if (CollectionUtils.isNotEmpty(peopleOfCharge)) {

            //分管用户角色<用户id,Set<RoleIds>>
            Map<String, Set<String>> userRoleIdsMap = userService.getAuthRolesByUser(peopleOfCharge);
            //分管用户部门<用户id，deptId>
            Map<String, String> userDeptMap = userService.getDeptIdByUserIds(peopleOfCharge);


            Set<AuthResource> resources = new HashSet<>();
            for (String userId : peopleOfCharge) {

                String deptId = userDeptMap.get(userId);

                Set<String> userRoleIds = userRoleIdsMap.get(userId);

                //获取用户资源，分管资源
                Set<AuthResource> userAuthResources = resourceSettingRelationService.getAuthResourcesByRoles(userId, deptId, userRoleIds, ResourceEnum.USER_OF_CHARGE);

                resources.addAll(userAuthResources);
            }

            authResources.addAll(resources);

        }

        return authResources;

    }

    /**
     * 根据用户id和用户部门code获取用户角色信息
     *
     * @param uid
     * @param deptCode
     * @return
     */
    public Set<AuthRole> getAuthRolesByUidAndDCode(String uid, String deptCode) {
        Set<AuthRole> authRolesUser = userService.getAuthRolesByUser(uid);
        log.info("用户的角色: " + authRolesUser.size());
        if (StringUtils.isNotBlank(deptCode)) {
            Set<Role> authRolesDept = departmentService.selectByCode(deptCode).getRoles();
            Set<AuthRole> authRoleSet = new HashSet<>();
            BeanMapper.deepCopy(authRolesDept, authRoleSet);
            log.info("部门的角色: " + authRolesDept.size());
            authRolesUser.addAll(authRoleSet);
        }
        return authRolesUser;
    }

    @Override
    public Set<AuthResource> getAuthResource(Authentication authentication) {
        // 部门
        String departmentCode = null;
        if (authentication.getPerson() != null) {
            departmentCode = authentication.getPerson().getDeptCode();
            log.info("部门 departmentCode: {} ", departmentCode);
        }
        String deptId = null;
        if (departmentCode != null) {
            deptId = departmentJpaDao.getDeptIdByCode(departmentCode);
        }

        Set<AuthRole> roles = authentication.getRoles();

        Set<String> roleIds = roles.stream().map(AuthRole::getId).collect(Collectors.toSet());

        //用户资源
        Set<AuthResource> userSelfResources = resourceSettingRelationService.getAuthResourcesByRoles(
                authentication.getUser().getId(), deptId, roleIds, ResourceEnum.SELF);


        // 分管用户的资源
        Set<AuthResource> authResourcesOfChargedUsers = getAuthResourcesOfChargedUsers(authentication.getPerson());
        userSelfResources.addAll(authResourcesOfChargedUsers);

        if (authentication.getPerson() != null) {
            //分管部门codes
            Set<String> deptCodesInCharge = authentication.getPerson().getCascadedAndChargedDeptCodes();
            if (CollectionUtils.isNotEmpty(deptCodesInCharge)) {
                //分管部门ids
                Set<String> deptIdsInCharge = departmentService.selectDeptIdByCode(deptCodesInCharge);
                if (CollectionUtils.isNotEmpty(deptIdsInCharge)) {
                    //分管部门的资源
                    Set<AuthResource> allDeptChargeResources = resourceSettingRelationService.getAuthResourcesByDids(deptIdsInCharge, DEPARTMENT_OF_CHARGE);
                    userSelfResources.addAll(allDeptChargeResources);
                }
            }
        }
        return userSelfResources;
    }

    @Override
    public List<AuthResource> loadResources(Authentication authentication, Set<String> resources, AuthzTypeEnum authzTypeEnum) {
        Set<AuthResource> userSelfResources = getAuthResource(authentication);
        //根据请求的resources 过滤
        Set<AuthResource> authResources = new HashSet<>();
        switch (authzTypeEnum) {
            case ANNOTATION:
                for (AuthResource authResource : userSelfResources) {

                    if (CollectionUtils.isNotEmpty(resources) && !resources.contains(authResource.getSign())) {
                        continue;
                    }
                    authResources.add(authResource);
                }
                break;
            case URI:
                for (AuthResource authResource : userSelfResources) {

                    if (CollectionUtils.isNotEmpty(resources) && !resources.contains(authResource.getUrl())) {
                        continue;
                    }
                    authResources.add(authResource);
                }
                break;
        }
        return new ArrayList<>(authResources);
    }
}
